package com.zh.flowable.web;


import com.zh.flowable.domain.ApiResult;
import com.zh.flowable.domain.AuditAction;
import com.zh.flowable.domain.dto.DeploymentWrapper;
import com.zh.flowable.domain.dto.ProcessDefinitionWrapper;
import com.zh.flowable.domain.dto.ProcessInstWrapper;
import com.zh.flowable.domain.dto.TaskCompleteReqForm;
import com.zh.flowable.domain.dto.TaskWrapper;
import com.zh.flowable.domain.flow.service.FlowableService;
import com.zh.flowable.domain.flow.service.HisProcessInstDetail;
import com.zh.flowable.domain.flow.service.ProcessInstDetail;
import jakarta.validation.Valid;
import org.apache.commons.lang3.StringUtils;
import org.flowable.engine.repository.Deployment;
import org.flowable.engine.repository.ProcessDefinition;
import org.flowable.engine.runtime.ProcessInstance;
import org.flowable.task.api.Task;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static com.zh.flowable.domain.ApiResult.fail;
import static com.zh.flowable.domain.ApiResult.success;
import static com.zh.mycommon.viewdata.ToWrapper.foreach;

/**
 * 工作流相关接口
 * <p>
 * <br/>
 * <p>
 * 补充：
 * <br/>
 * 每个Deployment包含至少一个ProcessDefinition，但一个ProcessDefinition可能被多个Deployment引用（例如版本更新时）
 * <br/>
 * 每个ProcessDefinition包含一个BPMN 2.0 XML文件，该文件定义了流程的结构和行为。
 * <br/>
 * 每个ProcessInstance表示流程的一次执行，它包含了流程的当前状态、变量、活动等信息。
 * <br/>
 * 每个Task表示流程中的一个活动，它包含了任务的定义、状态、变量等信息。
 * <br/>
 * 每个HistoryTask表示流程中的一个历史任务，它包含了任务的定义、状态、变量等信息。
 * <br/>
 * 每个HistoryProcessInstance表示流程的一次历史执行，它包含了流程的当前状态、变量、活动等信息。
 * </p>
 */
@RestController
@RequestMapping("/api/workflow")
public class WorkflowController {
    @Autowired
    private FlowableService flowableService;

    /**
     * 部署流程定义
     */
    @PostMapping("/deployment/deploy")
    public ApiResult deployProcess(@RequestBody Map<String, String> params) {
        String bpmnFile = params.get("bpmn");
        if (StringUtils.isEmpty(bpmnFile)) {
            return fail("参数bpmn不能为空");
        }
        String name = params.get("name");
        if (StringUtils.isEmpty(name)) {
            return fail("参数name不能为空");
        }
        String key = bpmnFile.split("\\.")[0];
        String id = flowableService.deployProcess(bpmnFile, name, key);

        return success(Map.of("key", key, "id", id));
    }

    /**
     * 删除流程部署，会级联删除流程定义
     *
     * @param deploymentId 部署ID
     */
    @DeleteMapping("/deployment/{deploymentId}")
    public ApiResult deleteDeployment(@PathVariable String deploymentId) {
        flowableService.deleteDeployment(deploymentId);
        return success();
    }

    /**
     * 获取部署列表
     */
    @GetMapping("/deployment-list")
    public ApiResult getDeployments() {
        List<Deployment> deployments = flowableService.getDeployments();

        return success(foreach(deployments, DeploymentWrapper::convertDTO));
    }

    /**
     * 获取流程定义列表
     */
    @GetMapping("/process-definitions")
    public ApiResult getProcessDefinitions() {
        List<ProcessDefinition> processDefinitions = flowableService.getProcessDefinitions();
        return success(foreach(processDefinitions, ProcessDefinitionWrapper::convertDTO));
    }

    /**
     * 启动流程
     */
    @PostMapping("/start/{key}")
    public ApiResult startProcess(@PathVariable("key") String processKey
            , @RequestBody Map<String, Object> variables) {
        //不同的流程需要不同的参数
        List<String> arguments = Process_Variables.getOrDefault(processKey, List.of());
        for (String argument : arguments) {
            if (!variables.containsKey(argument)) {
                return fail("参数" + argument + "不能为空");
            }
        }

        String applicant = (String) variables.get("applicant");
        ProcessInstance processInstance = flowableService.startProcess(processKey, applicant, variables);
        return success("流程审批单号: " + processInstance.getBusinessKey());
    }

    //不同的流程需要不同的参数
    static Map<String, List<String>> Process_Variables = new HashMap<>();

    static {
        Process_Variables.put("ask_leave", List.of("applicant", "days", "reason"));
        Process_Variables.put("raise_salary", List.of("applicant", "money", "reason"));
    }

    /**
     * 获取任务列表
     */
    @GetMapping("/tasks/{assignee}")
    public ApiResult getTasks(@PathVariable String assignee
            , @RequestParam(value = "is_group", required = false) String group) {
        List<Task> tasks = flowableService.getTasks(assignee, group != null);
        return success(foreach(tasks, TaskWrapper::convertDTO));
    }

    /**
     * 认领任务：先认领，才能完成任务
     */
    @PostMapping("/task/claim")
    public ApiResult claimTask(@RequestBody @Valid TaskCompleteReqForm form) {
        var taskId = form.getTaskId(); //任务ID
        var approver = form.getApprover(); //审批人

        if (StringUtils.isEmpty(taskId)) {
            return fail("参数taskId不能为空");
        }
        if (StringUtils.isEmpty(approver)) {
            return fail("参数approver不能为空");
        }

        flowableService.claimTask(taskId, approver, null);
        return success(null, "认领任务success");
    }

    /**
     * 取消认领任务
     */
    @PostMapping("/task/unclaim")
    public ApiResult unclaimTask(@RequestBody @Valid TaskCompleteReqForm form) {
        var taskId = form.getTaskId(); //任务ID
        var approver = form.getApprover(); //审批人

        flowableService.unclaimTask(taskId, approver);
        return success(null, "取消认领任务success");
    }

    /**
     * 完成任务：推动流程往下走
     */
    @PostMapping("/task/complete/{action}")
    public ApiResult completeTask(@PathVariable String action
            , @RequestBody @Valid TaskCompleteReqForm form) {
        var taskId = form.getTaskId(); //任务ID
        var approver = form.getApprover(); //审批人

        AuditAction auditAction;
        try {
            auditAction = AuditAction.valueOf(StringUtils.capitalize(action));
        } catch (IllegalArgumentException e) {
            return fail("参数action值错误");
        }

        flowableService.completeTask(taskId, auditAction, approver, form);
        return success(null, "任务已完成" + action);
    }

    /**
     * 强制完成任务：直接认领并审批通过
     */
    @PostMapping("/task/complete-force")
    public ApiResult forceCompleteTask(@RequestBody TaskCompleteReqForm form) {
        var taskId = form.getTaskId(); //任务ID
        var approver = form.getApprover(); //审批人
        var insteadApprover = form.getInsteadApprover(); // 代替审批人

        // 认领任务
        flowableService.claimTask(taskId, approver, insteadApprover);
        //完成任务: 审批通过
        return completeTask(AuditAction.Approve.name(), form);
    }

    /**
     * 删除流程实例
     */
    @DeleteMapping("/instance/{flowNo}")
    public ApiResult deleteProcessInstance(@PathVariable String flowNo) {

        flowableService.deleteProcessInstance(flowNo);
        return success();
    }

    /**
     * 查看流程实例详情
     */
    @GetMapping("/instance/detail")
    public ApiResult getProcessInstanceDetail(@RequestParam(value = "id", defaultValue = "") String id
            , @RequestParam(value = "no", defaultValue = "") String flowNo) {
        if (id.isEmpty() && flowNo.isEmpty()) {
            return fail("参数id或no不能为空");
        }

        ProcessInstDetail processInstDetail;
        if (!id.isEmpty()) {
            processInstDetail = flowableService.getProcessInstanceDetailById(id);
        } else {
            processInstDetail = flowableService.getProcessInstanceDetailByFlowNo(flowNo);
        }
        return success(ProcessInstWrapper.convertDTO(processInstDetail));
    }


    @GetMapping("/history/detail")
    public ApiResult getHistoryFlowDetail(@RequestParam("no") String flowNo) {
        HisProcessInstDetail processInstDetail = flowableService.getHistoryProcessInstDetailByFlowNo(flowNo);
        return success(ProcessInstWrapper.convertDTO(processInstDetail));
    }
}
